use salvo::oapi::extract::*;
use salvo::prelude::*;
use serde::Deserialize;
use validator::Validate;

use crate::{
    entities::rbac::departments::Model as Department,
    models::rbac::department::{DepartmentTree, SafeDepartment},
    service, JsonResult,
};

fn json_ok<T: serde::Serialize>(data: T) -> JsonResult<T> {
    Ok(Json(data))
}

#[derive(Debug, Deserialize, Validate, Extractible, ToSchema)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
pub struct CreateDepartmentData {
    #[validate(length(min = 2, message = "name length must be at least 2"))]
    pub name: String,
    pub parent_id: Option<i64>,
    #[validate(range(min = 0, message = "order_num must be positive"))]
    pub order_num: i32,
    pub code: String,
}

#[endpoint(tags("departments"))]
pub async fn create_department(
    req: &mut Request,
    data: JsonBody<CreateDepartmentData>,
) -> JsonResult<SafeDepartment> {
    let CreateDepartmentData {
        name,
        parent_id,
        order_num,
        code,
    } = data.into_inner();
    let db = service::pool();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req).unwrap();

    let createDepartment = Department {
        name,
        parent_id,
        order_num,
        tenant_id,
        create_by: "admin".to_string(),
    };

    let department = service::rbac::department::DepartmentService::create().await?;
    
    json_ok(SafeDepartment {
        id: department.id.to_owned(),
        name: department.name,
        order_num: department.order_num,
        department_id: department.department_id,
        parent_id: department.parent_id,
        tenant_id: department.tenant_id,
        create_by: department.create_by,
        create_time: department.create_time,
        update_by: department.update_by,
        update_time: department.update_time,
    })
}

#[derive(Debug, Deserialize, Validate, Extractible, ToSchema)]
#[salvo(extract(default_source(from = "body", parse = "json")))]
pub struct UpdateDepartmentData {
    #[validate(length(min = 2, message = "name length must be at least 2"))]
    pub name: String,
    pub parent_id: Option<i64>,
    pub code: String,
    pub department_id: Option<i64>,
    #[validate(range(min = 0, message = "order_num must be positive"))]
    pub order_num: i32,
}

#[endpoint(tags("departments"))]
pub async fn update_department(
    req: &mut Request,
    id: PathParam<String>,
    data: JsonBody<UpdateDepartmentData>,
) -> JsonResult<SafeDepartment> {
    let id = id.into_inner();
    let data = data.into_inner();
    let db = service::pool();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req)?.unwrap_or(0);
    let department = service::rbac::department::DepartmentService::update(
        db,
        &id,
        Some(&data.name),
        Some(data.department_id),
        Some(data.parent_id),
        Some(data.code),
        "admin",
        Some(tenant_id),
    )
    .await?;
    json_ok(SafeDepartment {
        id: department.id,
        tenant_id: department.tenant_id,
        department_id: department.department_id,
        name: department.name,
        parent_id: department.parent_id,
        order_num: department.order_num,
        create_by: department.create_by,
        create_time: department.create_time,
        update_by: department.update_by,
        update_time: department.update_time,
    })
}

#[endpoint(tags("departments"))]
pub async fn delete_department(
    req: &mut Request,
    id: PathParam<String>,
) -> Result<StatusCode, StatusError> {
    let id = id.into_inner();
    let db = service::conn();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req)
        .map_err(|e| StatusError::internal_server_error().brief(e.to_string()))?
        .unwrap_or(0);
    match service::rbac::department::DepartmentService::delete(db, &id, Some(tenant_id)).await {
        Ok(_) => Ok(StatusCode::NO_CONTENT),
        Err(e) => Err(StatusError::internal_server_error().brief(e.to_string())),
    }
}

#[endpoint(tags("departments"))]
pub async fn get_department(
    req: &mut Request,
    id: PathParam<String>,
) -> JsonResult<SafeDepartment> {
    let id = id.into_inner();
    let db = service::conn();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req)?.unwrap_or(0);
    let department =
        service::rbac::department::DepartmentService::find_by_id(db, &id, Some(tenant_id)).await?;
    json_ok(SafeDepartment {
        id: department.id,
        tenant_id: department.tenant_id,
        department_id: department.department_id,
        name: department.name,
        parent_id: department.parent_id,
        order_num: department.order_num,
        create_by: department.create_by,
        create_time: department.create_time,
        update_by: department.update_by,
        update_time: department.update_time,
    })
}

#[endpoint(tags("departments"))]
pub async fn list_departments(req: &mut Request) -> JsonResult<Vec<SafeDepartment>> {
    let db = service::conn();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req)?.unwrap_or(0);
    let departments =
        service::rbac::department::DepartmentService::find_all(db, Some(tenant_id)).await?;
    json_ok(departments)
}

#[endpoint(tags("departments"))]
pub async fn department_tree(req: &mut Request) -> JsonResult<Vec<DepartmentTree>> {
    let db = service::conn();
    use crate::utils::tenant::get_tenant_id;
    let tenant_id = get_tenant_id(req)?.unwrap_or(0);
    let tree = service::rbac::department::DepartmentService::find_tree(db, Some(tenant_id)).await?;
    json_ok(tree)
}

pub fn routes() -> Router {
    Router::new()
        .push(
            Router::with_path("/departments")
                .hoop(salvo::logging::Logger::new())
                .post(create_department)
                .get(list_departments),
        )
        .push(
            Router::with_path("/departments/<id>")
                .hoop(salvo::logging::Logger::new())
                .put(update_department)
                .delete(delete_department)
                .get(get_department),
        )
        .push(
            Router::with_path("/departments/tree")
                .hoop(salvo::logging::Logger::new())
                .get(department_tree),
        )
}
